/*
 * @Author: xiaosihan
 * @Date: 2024-08-20 11:35:33
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-09-02 19:21:35
 */

import JSZip from "jszip";
import {
  DefaultLoadingManager,
  Mesh,
  MeshStandardMaterial,
  Object3D,
} from "three";
import {
  ColladaLoader,
  DRACOLoader,
  FBXLoader,
  GLTFLoader,
  MTLLoader,
  OBJLoader,
  STLLoader,
} from "three/examples/jsm/Addons.js";
import exportGlb from "./exportGlb";
import exportGlTF from "./exportGlTF";
import exportObj from "./exportObj";
import utils from "@utils";

const objLoader = new OBJLoader();
const mtlLoader = new MTLLoader();
const gltfLoader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
const fbxLoader = new FBXLoader();
gltfLoader.setDRACOLoader(dracoLoader.setDecoderPath("./three_gltf/"));
const daeLoader = new ColladaLoader();
const stlLoader = new STLLoader();

// 资源列表
const blobs: { [key: string]: string } = {};

// 资源替换
DefaultLoadingManager.setURLModifier((url) => {
  const filename = url
    .replace(`blob:${location.origin}/`, "")
    .replace(/^\.\//, "");
  const new_url = blobs[filename] || url;
  return new_url;
});

// 格式转换方法
export default async function formatConvert<T extends File | File[]>(
  file: T,
  target: "gltf" | "glb" | "obj" = "glb",
  recursion: boolean = false
): Promise<T | undefined> {
  if (file instanceof Array) {
    file.map((f) => {
      blobs[f.webkitRelativePath.replace(/^[^\/]+\//, "")] =
        window.URL.createObjectURL(f);
    });
    const fileArray: Array<File> = [];
    for (let f of file) {
      if (
        ["glb", "gltf", "obj", "dae", "zip", "stl"].includes(
          utils.getSuffix(f.name)
        )
      ) {
        const new_file = await formatConvert(f as File, target, true);
        if (new_file) {
          fileArray.push(new_file);
        }
      }
    }
    return fileArray as T;
  }

  if (utils.getSuffix(file.name) === target) {
    return file; // 格式相同直接返回
  }

  if (!["glb", "gltf", "obj", "dae", "fbx", "stl"].includes(target)) {
    throw new Error(`暂不支持${target}格式`);
  }

  const threeObject3d = await loadMesh(file);

  // 如果是内部递归调用就不清理blob
  if (!recursion) {
    for (let url in blobs) {
      URL.revokeObjectURL(blobs[url]);
      delete blobs[url];
    }
  }

  if (threeObject3d) {
    switch (target) {
      case "glb":
        const glbArrayBuffer = await exportGlb(threeObject3d);
        return new File([glbArrayBuffer], file.name.replace(/\w+$/i, "glb"), {
          type: "application/octet-stream",
        }) as T;

      case "gltf":
        const gltfArrayBuffer = await exportGlTF(threeObject3d);
        return new File([gltfArrayBuffer], file.name.replace(/\w+$/i, "zip"), {
          type: "application/octet-stream",
        }) as T;

      case "obj":
        const objArrayBuffer = await exportObj(threeObject3d);
        return new File([objArrayBuffer], file.name.replace(/\w+$/i, "obj"), {
          type: "application/octet-stream",
        }) as T;

      default:
        break;
    }
  } else {
    return file;
  }

  return undefined;
}

// 公共的模型加载器
export async function loadMesh(file: File): Promise<Object3D | void> {
  const suffix = utils.getSuffix(file.name);

  const object = await new Promise<Object3D | void>(
    async (loadMeshResolve, reject) => {
      switch (suffix) {
        case "zip":
          await JSZip.loadAsync(file)
            .then(async (zip) => {
              for (let filename in zip.files) {
                if (!zip.files[filename].dir) {
                  // 不读文件夹
                  await zip
                    .file(filename)!
                    .async("blob")
                    .then((blob) => {
                      var blobURL = URL.createObjectURL(blob);
                      blobs[filename] = blobURL;
                    });
                }
              }

              // 如果有mtl文件就加载mtl文件
              let mtl = Object.keys(zip.files).find((filename) =>
                /\.mtl$/.test(filename)
              );
              if (mtl) {
                const materials = await mtlLoader.loadAsync(mtl);
                objLoader.setMaterials(materials);
              }

              //如果有obj就加载obj模型
              let obj = Object.keys(zip.files).find((filename) =>
                /\.obj$/.test(filename)
              );
              if (obj) {
                const objArraybuffer = await zip
                  .file(obj)
                  ?.async("arraybuffer")!;
                const objFile = new File([objArraybuffer], obj, {
                  type: "application/octet-stream",
                });
                const objObject3d = await loadMesh(objFile);
                loadMeshResolve(objObject3d);
              }

              //如果有gltf文件就加载gltf文件
              let gltf = Object.keys(zip.files).find((filename) =>
                /\.(gltf|glb)$/.test(filename)
              );
              if (gltf) {
                const gltfArraybuffer = await zip
                  .file(gltf)
                  ?.async("arraybuffer")!;
                const gltfFile = new File([gltfArraybuffer], gltf, {
                  type: "application/octet-stream",
                });
                const gltfObject3d = await loadMesh(gltfFile);
                loadMeshResolve(gltfObject3d);
              }

              //如果有dae文件就加载dae文件
              let dae = Object.keys(zip.files).find((filename) =>
                /\.dae$/.test(filename)
              );
              if (dae) {
                const daeArraybuffer = await zip
                  .file(dae)
                  ?.async("arraybuffer")!;
                const daeFile = new File([daeArraybuffer], dae, {
                  type: "application/octet-stream",
                });
                const daeObject3d = await loadMesh(daeFile);
                loadMeshResolve(daeObject3d);
              }

              //如果有fbx文件就加载fbx文件
              let fbx = Object.keys(zip.files).find((filename) =>
                /\.fbx$/.test(filename)
              );
              if (fbx) {
                const fbxArraybuffer = await zip
                  .file(fbx)
                  ?.async("arraybuffer")!;
                const fbxFile = new File([fbxArraybuffer], fbx, {
                  type: "application/octet-stream",
                });
                const fbxObject3d = await loadMesh(fbxFile);
                loadMeshResolve(fbxObject3d);
              }

              //如果有fbx文件就加载fbx文件
              let stl = Object.keys(zip.files).find((filename) =>
                /\.stl$/.test(filename)
              );
              if (stl) {
                const stlArraybuffer = await zip
                  .file(stl)
                  ?.async("arraybuffer")!;
                const stlFile = new File([stlArraybuffer], stl, {
                  type: "application/octet-stream",
                });
                const stlObject3d = await loadMesh(stlFile);
                loadMeshResolve(stlObject3d);
              }

              if ([obj, gltf, dae, fbx, stl].every((v) => !v)) {
                loadMeshResolve();
                console.error(
                  `${file.name}压缩包里面没有obj,glb,gltf,dae文件呢!`
                );
              }
            })
            .catch(function (err) {
              loadMeshResolve();
              console.error("读取ZIP文件失败:", err);
            });
          break;

        case "obj":
          blobs[file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name] =
            window.URL.createObjectURL(file);
          const [objObject3d] = await Promise.all([
            objLoader.loadAsync(
              file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name
            ),
            new Promise<void>((resolve, reject) => {
              DefaultLoadingManager.onLoad = () => {
                resolve();
              };
            }),
          ]);
          loadMeshResolve(objObject3d);
          break;

        case "glb":
        case "gltf":
          blobs[file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name] =
            window.URL.createObjectURL(file);
          const [gltfObject3d] = await Promise.all([
            gltfLoader.loadAsync(
              file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name
            ),
            new Promise<void>((resolve, reject) => {
              DefaultLoadingManager.onLoad = () => {
                resolve();
              };
            }),
          ]);
          gltfObject3d.scene.animations = gltfObject3d.animations;
          loadMeshResolve(gltfObject3d.scene);
          break;

        case "dae":
          blobs[file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name] =
            window.URL.createObjectURL(file);
          const [collada] = await Promise.all([
            daeLoader.loadAsync(
              file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name
            ),
            new Promise<void>((resolve, reject) => {
              DefaultLoadingManager.onLoad = () => {
                resolve();
              };
            }),
          ]);
          loadMeshResolve(collada.scene);
          break;

        case "fbx":
          blobs[file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name] =
            window.URL.createObjectURL(file);
          const [object3d] = await Promise.all([
            fbxLoader.loadAsync(
              file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name
            ),
            new Promise<void>((resolve, reject) => {
              DefaultLoadingManager.onLoad = () => {
                resolve();
              };
            }),
          ]);
          loadMeshResolve(object3d);
          break;

        case "stl":
          blobs[file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name] =
            window.URL.createObjectURL(file);
          const [stlObject3d] = await Promise.all([
            stlLoader
              .loadAsync(
                file.webkitRelativePath.replace(/^[^\/]+\//, "") || file.name
              )
              .then((geometry) => {
                const stlObject3d = new Mesh(
                  geometry,
                  new MeshStandardMaterial({
                    color: "#f1fbff",
                    metalness: 0.79,
                    roughness: 0.56,
                    envMapIntensity: 0.4,
                  })
                );
                return stlObject3d;
              }),
            new Promise<void>((resolve, reject) => {
              DefaultLoadingManager.onLoad = () => {
                resolve();
              };
            }),
          ]);
          loadMeshResolve(stlObject3d);
          break;

        default:
          break;
      }
    }
  );

  object &&
    object.traverse((o) => {
      const mesh = o as Mesh<any, MeshStandardMaterial>;
      if (mesh.isMesh) {
        const material = mesh.material as MeshStandardMaterial;
        if (material && material.emissiveMap) {
          material.emissive.set(1, 1, 1);
        }
      }
    });

  return object;
}
